use super::*;
use anyhow::Context;
use std::collections::VecDeque;
use std::fs;
use std::iter::zip;
use std::path::Path;

pub type Vis = Vec<Vec<bool>>;

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_float_eq() {
        let a = 0.1;
        let b = 0.2;

        assert!(float_eq(0.3, a + b));
    }
    // #[test]
    // fn cmp_point() {
    //     let line = Line::with_turple((100.0, 0.0), (50.0, 100.0));

    //     let point1 = Point::new(75.0, 50.0);
    //     assert!(line.on_line(point1));

    //     let point2 = Point::new(100.0, 0.0);
    //     assert!(line.on_line(point2));

    //     let point3 = Point::new(-20.0, -20.0);
    //     assert!(!line.on_line(point3));
    // }
    // #[test]
    fn test_line() {
        let line1 = Line::with_turple((-100.0, 0.0), (0.0, 100.0));
        let line2 = Line::with_turple((0.0, 50.0), (100.0, 0.0));

        assert!(!line1.intercept_line(line2), "Test 1 fail");

        let line1 = Line::with_turple((-100.0, 0.0), (0.0, 50.0));
        let line2 = Line::with_turple((-200.0, 0.0), (0.0, -100.0));

        assert!(!line1.intercept_line(line2), "Test 2 fail");

        let line1 = Line::with_turple((-200.0, 0.0), (0.0, -100.0));
        let line2 = Line::with_turple((-100.0, -50.0), (100.0, 100.0));

        assert!(line1.intercept_line(line2), "Test 3 fail");
    }
    // #[test]
    // fn intercept() {
    //     let line1 = Line::with_turple((-100.0, 0.0), (0.0, 50.0));
    //     assert!(!line1.intersect_squre((50.0, 0.0), 100.0));

    //     let line2 = Line::with_turple((-200.0, 0.0), (0.0, 200.0));
    //     assert!(line2.intersect_squre((-200.0, 0.0), 100.0));
    // }
}

#[inline]
pub fn float_eq(a: f32, b: f32) -> bool {
    ((a * 100.0) as i32) == ((b * 100.0) as i32)
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Point {
    pub x: f32,
    pub y: f32,
}
impl Point {
    #[inline]
    pub fn new(_x: f32, _y: f32) -> Point {
        Point { x: _x, y: _y }
    }
    #[inline]
    pub fn cmp(a: Point, b: Point) -> bool {
        if a.y < b.y {
            true
        } else if a.y == b.y {
            if a.x <= b.x {
                true
            } else {
                false
            }
        } else {
            false
        }
    }
}

impl From<(f32, f32)> for Point {
    #[inline]
    fn from((_x, _y): (f32, f32)) -> Self {
        Self { x: _x, y: _y }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Line {
    pub left: Point,
    pub right: Point,
}

impl Line {
    #[inline]
    pub fn with_turple(a: (f32, f32), b: (f32, f32)) -> Line {
        Self::new(Point::new(a.0, a.1), Point::new(b.0, b.1))
    }
    #[inline]
    pub fn new(a: Point, b: Point) -> Line {
        if Point::cmp(a, b) {
            Line { left: a, right: b }
        } else {
            Line { left: b, right: a }
        }
    }
    #[inline]
    pub fn get_kb(&self) -> (f32, f32) {
        let k = self.slope();
        (k, self.left.y - self.left.x * k)
    }
    #[inline]
    pub fn slope(&self) -> f32 {
        (self.right.y - self.left.y) / (self.right.x - self.left.x)
    }
    #[inline]
    pub fn get_intercept(&self, other: Line) -> (f32, f32) {
        let (k1, b1) = self.get_kb();
        let (k2, b2) = other.get_kb();

        let x = (-b1 + b2) / (k1 - k2);

        (x, k1 * x + b1)
    }
    #[inline]
    pub fn on_line(&self, point: Point) -> bool {
        self.left.x <= point.x
            && point.x <= self.right.x
            && self.left.y <= point.y
            && point.y <= self.right.y
    }
    #[inline]
    pub fn intercept_line(&self, other: Line) -> bool {
        if float_eq(self.slope(), other.slope()) {
            return false;
        }

        let point: Point = self.get_intercept(other).into();

        // println!("x : {} , y : {} ", point.x, point.y);

        if self.on_line(point) && other.on_line(point) {
            true
        } else {
            false
        }
    }
    #[inline]
    pub fn intersect_squre(&self, pt: Point, size: f32) -> bool {
        let points = [
            (pt.x, pt.y).into(),
            ((pt.x + size), pt.y).into(),
            ((pt.x + size), pt.y + size).into(),
            (pt.x, pt.y + size).into(),
        ];

        let list = [
            Line::new(points[0], points[1]),
            Line::new(points[1], points[2]),
            Line::new(points[2], points[3]),
            Line::new(points[3], points[0]),
        ];

        for line in list {
            if self.intercept_line(line) {
                return true;
            }
        }

        return false;
    }
}

impl MapGen {
    #[inline]
    pub fn new(mut _config: GenConfig) -> anyhow::Result<Self> {
        let _rng = if let Some(seed) = _config.random_seed.as_ref() {
            ChaCha8Rng::seed_from_u64(*seed)
        } else {
            _config.random_seed = Some(rand::random());
            ChaCha8Rng::seed_from_u64(_config.random_seed.unwrap())
        };

        let _base = {
            let mut vec = Vec::new();
            vec.resize_with(_config.m, || {
                let mut vec = Vec::new();
                vec.resize(
                    _config.n,
                    Block {
                        is_land: LandOrWater::Land,
                        height: 0,
                        ramp: Default::default(),
                        captured: Captured::None,
                        close_to_low: false,
                        belong_to: 0,
                    },
                );
                vec
            });
            vec
        };

        let mut template_list = Vec::new();
        let mut object_count = Vec::new();

        let object_dir = Path::new("./objects").to_path_buf();
        for element in _config.objects.iter() {
            let mut object_path = object_dir.clone();
            object_path.push(element.file.clone());
            object_path.set_extension("json");

            let object_file = fs::read_to_string(&object_path)
                .with_context(|| format!("Fail to read the object file {:?} !", &object_path))?;
            let mut object_template: ObjectTemplate = serde_json::from_str(&object_file)
                .with_context(|| {
                    format!(
                        "Fail to parse the object file ! Path: {:?} \nFile content : {} ",
                        &object_path, &object_file
                    )
                })?;

            for (x, y) in object_template.area.iter_mut() {
                *x = *x / 10.0;
                *y = (*y / 10.0) * -1.0;
            }

            object_template.gen_lines();
            object_template.id = Some(element.file.clone());

            template_list.push(object_template);
            object_count.push(element.count);
        }

        Ok(Self {
            base: _base,
            rng: _rng,
            config: _config,
            parsed: None,
            templates: template_list,
            object_count: object_count,
        })
    }
    #[inline]
    pub fn base_size(&self) -> (usize, usize) {
        (self.config.m, self.config.n)
    }
    #[inline]
    pub fn set_player(&mut self) -> GenResult<()> {
        let mut available = Vec::new();

        for (i, element) in self.base.iter().enumerate() {
            for (j, element) in element.iter().enumerate() {
                if element.captured == Captured::None {
                    available.push((i, j));
                }
            }
        }

        for i in 0..self.config.players {
            if available.len() == 0 {
                return Err(GenError::NoBlocksSetPlayer(i));
            }

            let selected = self.rng.gen_range(0..available.len());
            let (x, y) = available[selected];

            self.base[x][y].captured = Captured::PlayerSpawn;
            available.remove(selected);
        }

        Ok(())
    }
    #[inline]
    pub fn display_base(&self) {
        print!("  ");
        for i in 0..self.config.n {
            if i < 10 {
                print!(" ");
            }
            print!("{} ", i);
        }
        print!("   ");
        for i in 0..self.config.n {
            if i < 10 {
                print!(" ");
            }
            print!("{} ", i);
        }
        print!("      ");
        for i in 0..self.config.n {
            if i < 10 {
                print!("  ");
            } else if i < 100 {
                print!(" ");
            }
            print!("{} ", i);
        }
        println!("     ");

        for (i_index, i) in self.base.iter().enumerate() {
            if i_index < 10 {
                print!(" ");
            }
            print!("{} ", i_index);
            for j in i.iter() {
                let c = match j.captured {
                    Captured::None => {
                        if j.is_land == LandOrWater::Land {
                            print!("{}  ", j.height);
                        } else {
                            print!("o  ");
                        }

                        continue;
                    }
                    Captured::PlayerSpawn => 'P',
                    Captured::Ramp => match j.ramp {
                        Ramp::Up => '^',
                        Ramp::Down => 'v',
                        Ramp::Left => '<',
                        Ramp::Right => '>',
                        Ramp::None => panic!("The ramp should not be none !"),
                    },
                };
                print!("{}  ", c);
            }

            if i_index < 10 {
                print!(" ");
            }
            print!("{} ", i_index);
            for j in i.iter() {
                if j.is_land == LandOrWater::Land {
                    print!("{}  ", j.height);
                } else {
                    print!("o  ");
                }
            }
            print!("  ");

            if i_index < 10 {
                print!(" ");
            }
            print!("{}   ", i_index);
            for j in i.iter() {
                // if j.is_land == LandOrWater::Land {
                //     print!("{}  ", j.height);
                // } else {
                //     print!("o  ");
                // }

                print!("{}", j.belong_to);
                if j.belong_to < 10 {
                    print!("   ");
                } else if j.belong_to < 100 {
                    print!("  ");
                }
            }
            println!("");
        }
    }
    #[inline]
    pub fn new_vis(&self) -> Vec<Vec<bool>> {
        let (m, n) = self.base_size();
        let vis = {
            let mut temp = Vec::with_capacity(m);
            temp.resize_with(m, || {
                let mut temp2 = Vec::with_capacity(n);
                temp2.resize(n, false);
                temp2
            });
            temp
        };

        vis
    }
    #[inline]
    pub fn display_error(&self, err: GenError) -> String {
        use GenError::*;
        match err {
            NoBlocksSetObject(index) => {
                format!(
                    "No block to set the object  {} !",
                    self.templates[index].id.as_ref().unwrap()
                )
            }
            _ => {
                format!("{}", err)
            }
        }
    }
    #[inline]
    pub fn std_bfs<C, F>(&mut self, option: BfsOption<C, F>) -> GenResult<C> {
        let mut que = VecDeque::new();
        que.push_back((option.startx, option.starty));

        let (m, n) = self.base_size();

        let vis;
        let mut vis_buf = None;
        if let Some(vis_ref) = option.vistied_map {
            vis = vis_ref;
            drop(vis_buf);
        } else {
            vis_buf = Some(self.new_vis());
            vis = vis_buf.as_mut().unwrap();
        }

        vis[option.startx][option.starty] = true;

        let mut count = (*option.init)(self, (option.startx, option.starty), &option.arg);

        while let Some((ox, oy)) = que.pop_front() {
            let (x, y) = (ox as i32, oy as i32);

            for (dx, dy) in zip(DX.iter(), DY.iter()) {
                let (nx, ny) = if point_in_map((x + *dx, y + *dy), (m, n)) {
                    ((x + *dx) as usize, (y + *dy) as usize)
                } else {
                    continue;
                };
                if !vis[nx][ny] && (*option.filter)(self, (nx, ny), &option.arg) {
                    vis[nx][ny] = true;

                    if !(*option.block_action)(self, (nx, ny), &mut count, &option.arg) {
                        break;
                    }

                    if option.is_rand {
                        que.insert(self.rng.gen_range(0..=que.len()), (nx, ny));
                    } else {
                        que.push_back((nx, ny));
                    }
                }
            }
        }

        (*option.end_action)(self, &mut count, &option.arg)?;

        Ok(count)
    }
}

pub struct BfsOption<'a, C, F> {
    pub startx: usize,
    pub starty: usize,
    pub arg: F,
    pub vistied_map: Option<&'a mut Vis>,
    pub init: Box<dyn Fn(&mut MapGen, (usize, usize), &F) -> C>,
    pub filter: Box<dyn Fn(&MapGen, (usize, usize), &F) -> bool>,
    pub block_action: Box<dyn Fn(&mut MapGen, (usize, usize), &mut C, &F) -> bool>,
    pub end_action: Box<dyn Fn(&mut MapGen, &mut C, &F) -> GenResult<()>>,
    pub is_rand: bool,
}
